/*
* Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  NfcNppExample.
*/

#include "nfcnppexample.h"

#include <QUrl>

#include <qllcpserver.h>
#include <qndefrecord.h>
#include <qndefnfctextrecord.h>
#include <qndefnfcurirecord.h>
#include <qnearfieldmanager.h>

// Constants
const quint8 NPPVersion = 1;
const quint8 NPPActionCode = 1;
const QLatin1String NPPServiceName("com.android.npp");

NfcNppExample::NfcNppExample(QObject *parent) :
    QObject(parent)
{
    nfcManager = new QNearFieldManager(this);

    connect(nfcManager, SIGNAL(targetDetected(QNearFieldTarget*)),
            this, SLOT(handleTargetDetected(QNearFieldTarget*)));
    connect(nfcManager, SIGNAL(targetLost(QNearFieldTarget*)),
            this, SLOT(handleTargetLost(QNearFieldTarget*)));

    // Only detect other NFC devices. Leave the phone to handle NFC tags.
    nfcManager->setTargetAccessModes(QNearFieldManager::NdefReadTargetAccess | QNearFieldManager::NdefWriteTargetAccess);
    nfcManager->startTargetDetection(QNearFieldTarget::NfcForumDevice);

    nfcServer = new QLlcpServer(this);
    connect(nfcServer, SIGNAL(newConnection()), this, SLOT(handleNewConnection()));
    nfcServer->listen(NPPServiceName);

    nfcClientSocket = new QLlcpSocket(this);
    connect(nfcClientSocket, SIGNAL(stateChanged(QLlcpSocket::SocketState)), this, SLOT(handleSocketStateChanged(QLlcpSocket::SocketState)));

    // QueuedConnection is used to close the connection on the next event loop event.
    connect(this, SIGNAL(closeConnection()), this, SLOT(handleCloseConnection()), Qt::QueuedConnection);
}

NfcNppExample::~NfcNppExample()
{
    // empty implementation.
}

void NfcNppExample::handleTargetDetected(QNearFieldTarget *target)
{
    // Check if the target supports LLCP access
    QNearFieldTarget::AccessMethods accessMethods = target->accessMethods();
    if (accessMethods.testFlag(QNearFieldTarget::LlcpAccess)) {
        nfcClientSocket->connectToService(target, NPPServiceName);
    }
}

void NfcNppExample::handleTargetLost(QNearFieldTarget *target)
{
    nfcClientSocket->disconnectFromService();
    target->deleteLater();
}


void NfcNppExample::handleNewConnection()
{
    if (nfcServerSocket) {
        nfcServerSocket->deleteLater();
    }

    // The socket is a child of the server and will therefore be deleted automatically
    nfcServerSocket = nfcServer->nextPendingConnection();
    connect(nfcServerSocket, SIGNAL(readyRead()), this, SLOT(readMessage()));    
}

void NfcNppExample::handleSocketStateChanged(QLlcpSocket::SocketState socketState)
{
    switch ( socketState )
    {
    case QLlcpSocket::ConnectedState:
        // Connection opened.
        emit targetConnected();
        
        //Try to send the message.
        if (nfcClientSocket && nfcClientSocket->isOpen()) {
            nfcClientSocket->write(ndefMessage);
            emit closeConnection();
        }
        break;
    default:
        break;
    }
}

void NfcNppExample::readMessage()
{
    QByteArray rawData = nfcServerSocket->readAll();

    QList<QNdefMessage> messages;
    messages = parseNppMessage(rawData);
    QNdefMessage message = messages[0];

    foreach (const QNdefRecord &record, message) {
        if (record.isRecordType<QNdefNfcUriRecord>()) {
            QNdefNfcUriRecord uriRecord(record);
            emit messageReceived(record.type(), uriRecord.uri().toString());
        } else if (record.isRecordType<QNdefNfcTextRecord>()) {
            QNdefNfcTextRecord textRecord(record);
            emit messageReceived(record.type(), textRecord.text());
        }
    }
}

void NfcNppExample::sendMessage(const QString &type, const QString &text)
{
    // Buffers the message.
    QNdefMessage message;
    if (type.compare("U") == 0) {
        QNdefNfcUriRecord uriRecord;
        uriRecord.setUri(QUrl(text));
        message.append(uriRecord);
    } else {
        QNdefNfcTextRecord textRecord;
        textRecord.setText(text);
        message.append(textRecord);
    }

    QList<QNdefMessage> messageArray;
    messageArray.append(message);
    ndefMessage = createNppMessage(messageArray);
}

QList<QNdefMessage> NfcNppExample::parseNppMessage(const QByteArray &array) const
{
    QList<QNdefMessage> messages;

    QByteArray::const_iterator i = array.begin();
    while (i < array.constEnd()) {
        quint8 version = *i;

        if (version != NPPVersion) {
            return messages;
        }

        quint32 messageCount = 0;
        messageCount = quint8(*(++i)) << 24;
        messageCount |= quint8(*(++i)) << 16;
        messageCount |= quint8(*(++i)) << 8;
        messageCount |= quint8(*(++i)) << 0;

        if (messageCount ==  1) {
            quint8 actionCode = *(++i);
            quint32 ndefLength = quint8(*(++i)) << 24;
            ndefLength |= quint8(*(++i)) << 16;
            ndefLength |= quint8(*(++i)) << 8;
            ndefLength |= quint8(*(++i)) << 0;

            QByteArray ndef(++i, ndefLength);
            QNdefMessage message = QNdefMessage::fromByteArray(ndef);
            messages.append(message);
            i += ndefLength;
        }
    }
    return messages;
}

QByteArray NfcNppExample::createNppMessage(const QList<QNdefMessage> &messages) const
{
    QByteArray message;

    if (messages.count() != 1) {
        return message;
    }

    message.append(NPPVersion);

    quint32 messageCount = messages.count();
    message.append(messageCount >> 24);
    message.append(messageCount >> 16);
    message.append(messageCount >> 8);
    message.append(messageCount);

    // Only supported action code is 0x01 and only one NDEF entry
    // with the action code 0x01 is allowed.
    message.append(NPPActionCode);
    QByteArray entry = messages[0].toByteArray();
    quint32 ndefLength = entry.length();
    message.append(ndefLength >> 24);
    message.append(ndefLength >> 16);
    message.append(ndefLength >> 8);
    message.append(ndefLength);

    message.append(entry);
    return message;
}

void NfcNppExample::handleCloseConnection()
{
    // Disconnect the open connection.
    if (nfcClientSocket && nfcClientSocket->isOpen()) {
        nfcClientSocket->disconnectFromService();
    }
}
